﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Diagnostics;

using ThreeByte.Security;
using ThreeByte.ActiveDeck.Data;
using ThreeByte.ActiveDeck.Service;
using System.Net;
using System.Web.Security;
using System.Web.Profile;
using System.Reflection;
using ThreeByte.ActiveDeck.Data.UserResponse;
using System.Collections.Specialized;
using System.Web;
using System.Web.Hosting;
using log4net;
using log4net.Config;
using System.ServiceModel.Activation;
using ThreeByte.ActiveDeck.Web.Data;
using ThreeByte.ActiveDeck.Publish;
using ThreeByte.ActiveDeck.Data.UserResponse.Report;
using System.IO.Compression;

namespace ThreeByte.ActiveDeck.Web
{
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class ActiveDeckConfigService : IActiveDeckConfigService
    {
        private static readonly XNamespace ns = Service.ServiceConfiguration.NAMESPACE;
        private static readonly ILog log = LogManager.GetLogger(typeof(ActiveDeckConfigService));

        static ActiveDeckConfigService() {
            //Initialize log4net subsystem
            XmlConfigurator.Configure();
            log.Info("Config Service loaded");
        }

        public XElement UploadResource(string presentation, string fileName, XElement resourceData) {
            log.InfoFormat("UploadResource: {0}/{1}", presentation, fileName);

            //Verify the user/password
            Console.WriteLine(System.Web.HttpRuntime.AppDomainAppPath);
            string appDirectory = System.Web.HttpRuntime.AppDomainAppPath;
            string dataDir = Path.Combine(appDirectory, "presentations");
            string presentationDir = Path.Combine(dataDir, presentation);
            
            try {
                XElement authNode = resourceData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(authNode));
                //AuthorizationUtil.Authorize(AuthorizationUtil.Authenticate(cred));

                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to publish this presentation");
                }

                //Store to Azure
                byte[] resourceBytes = Convert.FromBase64String(resourceData.Value);
                string fileType = resourceData.Attribute("Type").Value;
                CloudStorageUtil.StoreResource(presentation, fileName, resourceBytes, fileType);

                //TODO: Verify size?
                if(resourceBytes.Length != int.Parse(resourceData.Attribute("Size").Value)) {
                    throw new Exception("Filesize corruption");
                }
                return SuccessMessage();
            } catch(Exception ex) {
                log.Warn("UploadResource Error", ex);
                return ErrorMessage(ex);
            }
        }

        public XElement UploadResourceBlock(string presentation, string fileName, string blockId, string contentHash, XElement resourceData) {
            log.InfoFormat("UploadResourceBlock: {0}/{1}/{2} [{3}]", presentation, fileName, blockId, contentHash);

            //Verify the user/password
            Console.WriteLine(System.Web.HttpRuntime.AppDomainAppPath);
            string appDirectory = System.Web.HttpRuntime.AppDomainAppPath;
            string dataDir = Path.Combine(appDirectory, "presentations");
            string presentationDir = Path.Combine(dataDir, presentation);

            try {
                XElement authNode = resourceData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(authNode));
                //AuthorizationUtil.Authorize(AuthorizationUtil.Authenticate(cred));

                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to publish this presentation");
                }

                //Store to Azure
                byte[] resourceBytes = Convert.FromBase64String(resourceData.Value);
               
                CloudStorageUtil.StoreResourceBlock(presentation, fileName, resourceBytes, blockId, contentHash);

                //TODO: Verify size?
                if(resourceBytes.Length != int.Parse(resourceData.Attribute("Size").Value)) {
                    throw new Exception("Filesize corruption");
                }
                return SuccessMessage();
            } catch(Exception ex) {
                log.Warn("UploadResource Error", ex);
                return ErrorMessage(ex);
            }
        }

        public XElement CommitResourceBlocks(string presentation, string fileName, XElement blockList) {
            log.InfoFormat("CommitResourceBlocks: {0}/{1}", presentation, fileName);

            //Verify the user/password
            Console.WriteLine(System.Web.HttpRuntime.AppDomainAppPath);
            string appDirectory = System.Web.HttpRuntime.AppDomainAppPath;
            string dataDir = Path.Combine(appDirectory, "presentations");
            string presentationDir = Path.Combine(dataDir, presentation);

            try {
                XElement authNode = blockList.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(authNode));

                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to publish this presentation");
                }

                
                BlockList newBlockList = BlockList.Parse(blockList);
                XElement resourceHeader = blockList.Element(ns + "Graphic");
                string fileType;
                //TODO: Delete this test when Graphic is obsolete
                if(resourceHeader != null) {
                    fileType = resourceHeader.Attribute("Type").Value;
                } else {
                    resourceHeader = blockList.Element(ns + "Media");
                    fileType = resourceHeader.Attribute("Type").Value;
                }
                CloudStorageUtil.CommitResourceBlock(presentation, fileName, newBlockList.BlockIDs, fileType);

                return SuccessMessage();
            } catch(Exception ex) {
                log.Warn("UploadResource Error", ex);
                return ErrorMessage(ex);
            }
        }

        public Stream GetResource(string presentation, string fileName, XElement auth) {
            Console.WriteLine("Got the file: " + fileName + presentation);

            //try {
            MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
            //AuthorizationUtil.Authorize(AuthorizationUtil.Authenticate(cred));

            if(!(AuthorizationUtil.IsAuthorizedParticipant(presentation, user.UserName))) {
                throw new Exception("User is not authorized to view this resource");
            }


            string contentType = null;
            //Retrieve from Azure
            Stream dataStream = CloudStorageUtil.GetResource(presentation, fileName, out contentType);
            WebOperationContext.Current.OutgoingResponse.ContentType = contentType;
            return dataStream;
            //} catch(Exception ex) {
            //    return ErrorMessage(ex);
            //}

        }

        public XElement GetResourceList(string presentation, string count, string skip, XElement auth) {

            MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

            if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                throw new Exception("User is not authorized to view this resource");
            }

            int countInt = 0;
            int.TryParse(count, out countInt);
            int skipInt = 0;
            int.TryParse(skip, out skipInt);
            return CloudStorageUtil.GetResourceList(presentation, countInt, skipInt);

        }

        public XElement DeleteResources(string presentation, XElement data) {

            try {
                XElement authNode = data.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(authNode));

                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to publish this presentation");
                }

                foreach(XElement res in data.Elements(ns + "Resource")) {
                    Guid id = Guid.Parse(res.Attribute("ID").Value);

                    CloudStorageUtil.DeleteResource(presentation, id);

                }

                return SuccessMessage();
            } catch(Exception ex) {
                log.Warn("DeleteResources Error", ex);
                return ErrorMessage(ex);
            } 
            
        }

        public XElement GetResourceInfo(string presentation, string resource, XElement auth) {
            
            MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
            
            if(!(AuthorizationUtil.IsAuthorizedParticipant(presentation, user.UserName))) {
                throw new Exception("User is not authorized to access this resource");
            }

            //Retrieve from Azure
            XElement resourceInfo = CloudStorageUtil.GetResourceInfo(presentation, resource);
            return resourceInfo;
        }

        public XElement Publish(string presentation, XElement presentationData) {

            XElement auth = presentationData.Element(ns + "Authentication");
            //TODO: Verify that the presentation id matches
            string presentationId = presentationData.Attribute("ID").Value;
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                //Verify user can modify Presentation
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to publish this presentation");
                }

                //TODO: Verify All resources


                //Update the db object
                PresentationHeader pres = new PresentationHeader(DataUtil.UpdatePresentationConfig(presentation, presentationData));

                return pres.ToXml();
            } catch(Exception ex) {
                log.Error("Publish Error", ex);
                return ErrorMessage(ex);
                //throw ex;
            }
        }

        public XElement PublishCompressed(string presentation, XElement presentationData) {

            XElement auth = presentationData.Element(ns + "Authentication");
            //TODO: Verify that the presentation id matches
            string presentationId = presentationData.Attribute("ID").Value;
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                //Verify user can modify Presentation
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to publish this presentation");
                }
               
                //Get Bytes
                byte[] presentationBytes = Convert.FromBase64String(presentationData.Value);

                string presentationString;
                //Decompress to string
                using(MemoryStream outStream = new MemoryStream()) {
                    using(MemoryStream inStream = new MemoryStream(presentationBytes)) {
                        using(GZipStream zipStream = new GZipStream(inStream, CompressionMode.Decompress)) {
                            byte[] buf = new byte[4096];
                            int bytesRead = zipStream.Read(buf, 0, buf.Length);
                            while(bytesRead > 0) {
                                outStream.Write(buf, 0, bytesRead);
                                bytesRead = zipStream.Read(buf, 0, buf.Length);
                            }
                        }
                    }
                    byte[] decompressedBytes = outStream.ToArray();
                    presentationString = Encoding.UTF8.GetString(decompressedBytes);
                }

                XElement presentationNode = XElement.Parse(presentationString);
                
                //Store to Database
                //Update the db object
                PresentationHeader pres = new PresentationHeader(DataUtil.UpdatePresentationConfig(presentation, presentationNode));

                return pres.ToXml();
            } catch(Exception ex) {
                log.Error("Publish Error", ex);
                return ErrorMessage(ex);
                //throw ex;
            }
        }

        public XElement GetPresentation(string presentation, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                //Verify user can view Presentation - Make a request if not yet approved
                if(!(AuthorizationUtil.IsAuthorizedParticipant(presentation, user.UserName, true))) {
                    if(AuthorizationUtil.IsUserBlockedForPresentation(presentation, user.UserName)) {
                        return ErrorMessage("User is blocked");
                    }
                    return ErrorMessage("User is not authorized to view this presentation");
                }

                int version = 0;
                XElement presentationConfig = DataUtil.GetPresentationConfig(presentation, ref version);
                AuthorizationUtil.RegisterParticipantDownload(presentation, version, user.UserName);
                return presentationConfig;
            } catch(Exception ex) {
                return ErrorMessage(ex);
                //throw ex;
            }
        }

        public XElement GetPresentationInfo(string presentation) {
            try {
                XElement presInfo = DataUtil.GetPresentationInfo(presentation);
                return presInfo;
            } catch(Exception) {
                return ErrorMessage("Invalid Presentation ID", "Configuration");
            }
        }

        public Stream GetPresentationLogo(string presentation) {
            
            //See if there is an existing logo,
            try {
                string contentType = null;
                Stream logoStream = CloudStorageUtil.GetResource(presentation, (new Guid()).ToString(), out contentType);

                WebOperationContext.Current.OutgoingResponse.ContentType = contentType;
                return logoStream;
            } catch(Exception ex) {
                //If this throws, just return the default
                log.Info("Returning default Logo", ex);
                WebOperationContext.Current.OutgoingResponse.ContentType = "image/png";
                return Assembly.GetExecutingAssembly().GetManifestResourceStream("ThreeByte.ActiveDeck.Web.Assets.ad_logo_96.png");
            }
        }

        public static MembershipUser Authenticate(NetworkCredential cred) {
            if(!(Membership.ValidateUser(cred.UserName, cred.Password))) {
                throw new Exception("Invalid Username or Password");
            }

            return Membership.GetUser(cred.UserName);
        }

        public XElement NewUser(XElement accountInfo) {
            log.InfoFormat("CreateNewUser {0}", accountInfo);
            //Membership.DeleteUser("chris@3-byte.com");

            try {
                string username = accountInfo.Attribute("UserName").Value;
                string password = accountInfo.Attribute("Password").Value;
                string firstname = accountInfo.Attribute("FirstName").Value;
                string lastname = accountInfo.Attribute("LastName").Value;
                string organization = accountInfo.Attribute("Organization").Value;
                //string role = accountInfo.Attribute("Level").Value;
                string role = @"Participant";  //Users can only register as participants until they provide billing info

                MembershipCreateStatus createStatus;
                MembershipUser user = Membership.CreateUser(username, password, username, null, null, true, out createStatus);

                if(createStatus != MembershipCreateStatus.Success) {
                    return ErrorMessage("Cannot create user: " + createStatus.ToString(), "Authentication");
                }

                ProfileBase profile = ProfileBase.Create(user.Email, true);
                profile.SetPropertyValue("FirstName", firstname);
                profile.SetPropertyValue("LastName", lastname);
                profile.SetPropertyValue("Organization", organization);
                profile.Save();

                Roles.AddUserToRole(username, role);
                return SuccessMessage();
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement EmailSignup(Stream stream) {
            //Parse the Form elements
            StreamReader sr = new StreamReader(stream);
            NameValueCollection qs = HttpUtility.ParseQueryString(sr.ReadToEnd());
            sr.Dispose();

            string emailAddress = qs["email_address"];
            Console.WriteLine("The email address is {0}", emailAddress);
            Guid key = DataUtil.RecordDownloadRequest(emailAddress);
            EmailUtil.SendInvitationEmail(emailAddress, key);

            return SuccessMessage();
        }

        public XElement GetAddInVersion() {

            XElement versionNode = new XElement(ns + "AddIn");
            Version ver = Version.Parse(DataUtil.GetConfigValue("AddInVersion"));
            versionNode.Add(new XAttribute("Version", ver));
            DateTime updated = DateTime.Parse(DataUtil.GetConfigValue("AddInUpdated"));
            versionNode.Add(new XAttribute("Updated", updated));
            versionNode.Add(new XAttribute("URI", string.Format("{0}/Download", ServiceConfiguration.SERVICE_ENDPOINT)));

            return versionNode;
        }

        public Stream DownloadAddIn(string responseGuid) {

            Guid key = Guid.Parse(responseGuid);

            //Verify the key liveness
            if(!(DataUtil.IsDownloadKeyValid(key))) {
                Uri redirectUri = new Uri(string.Format("{0}/GetStarted.aspx", ServiceConfiguration.WEB_HOST));
                string errPage = ErrorPage("This link is no longer valid", redirectUri);
                MemoryStream stream = new MemoryStream(ASCIIEncoding.ASCII.GetBytes(errPage));
                return stream;
            }

            string username = DataUtil.GetEmailForDownloadKey(key);
            DataUtil.RecordDownload(username, DataUtil.GetConfigValue("AddInVersion"));

            return GetDownloadStream(WebOperationContext.Current.OutgoingResponse);
        }

        public Stream DownloadAddInWithAuth(XElement auth) {

            MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

            if(!UserHasPermission(user.UserName, "Presenter")) {
                log.WarnFormat("user is not allowd to download file: {0}", user.UserName);
                throw new Exception("User is not allowed to download this file");
            }

            DataUtil.RecordDownload(user.UserName, DataUtil.GetConfigValue("AddInVersion"));

            return GetDownloadStream(WebOperationContext.Current.OutgoingResponse);
        }

        private Stream GetDownloadStream(OutgoingWebResponseContext context) {
            string EXE_NAME = @"ActiveDeck Setup.exe";
            string resourceName = string.Format("{0}/{1}", DataUtil.GetConfigValue("AddInVersion"), EXE_NAME);
            Stream dataStream = CloudStorageUtil.GetDownload("installers", resourceName);
            context.ContentType = "application/octet-stream";
            context.Headers.Add("Content-Disposition", string.Format("attachment; filename=\"{0}\"", EXE_NAME));
            context.Headers.Add("Content-Length", string.Format("{0}", dataStream.Length));
            return dataStream;
        }

        public Stream DownloadBetaAddIn(string version) {

            string EXE_NAME = @"ActiveDeck Setup.exe";
            string resourceName = string.Format("beta/{0}/{1}", version, EXE_NAME);
            Stream dataStream = CloudStorageUtil.GetDownload("installers", resourceName);
            OutgoingWebResponseContext context = WebOperationContext.Current.OutgoingResponse;
            context.ContentType = "application/octet-stream";
            context.Headers.Add("Content-Disposition", string.Format("attachment; filename=\"{0}\"", EXE_NAME));
            context.Headers.Add("Content-Length", string.Format("{0}", dataStream.Length));
            return dataStream;
        }

        public XElement TestLogin(XElement auth) {

            try {
                NetworkCredential cred = AuthorizationUtil.FromAuthenticationXML(auth);

                bool isValid = Membership.ValidateUser(cred.UserName, cred.Password);
                if(isValid) {
                    return SuccessMessage();
                }
            } catch(Exception ex) {
                //throw ex;
                Console.WriteLine(ex.Message);
                return ErrorMessage(ex);
            }
            return ErrorMessage("Login Failed", "Authentication");
        }


        public XElement AccountInfo(string username, XElement auth) {

            //All requests must come from a valid user
            NetworkCredential cred = AuthorizationUtil.FromAuthenticationXML(auth);
            if(!(Membership.ValidateUser(cred.UserName, cred.Password))){
                return ErrorMessage("Invalid Username or Password", "Authentication");
            }

            MembershipUser requestingUser = Membership.GetUser(cred.UserName);

            //Allow this request if it is the same user, or a high-level user
            if(username != requestingUser.UserName){
                if(!(Roles.IsUserInRole(requestingUser.UserName, "Developer")
                    || Roles.IsUserInRole(requestingUser.UserName, "Guru"))) {
                        return ErrorMessage("This user does not have permission to access user's Account Information", "Authorization");
                }
            }

            MembershipUser user = Membership.GetUser(username);

            XElement accountInfo = new XElement("AccountInfo");
            ProfileBase profile = ProfileBase.Create(user.UserName);
            accountInfo.Add(new XAttribute("UserName", user.UserName));
            accountInfo.Add(new XAttribute("FirstName", profile["FirstName"]));
            accountInfo.Add(new XAttribute("LastName", profile["LastName"]));
            accountInfo.Add(new XAttribute("Organization", profile["Organization"]));
            accountInfo.Add(new XAttribute("Level", GetUserLevel(user.UserName)));
            return accountInfo;
        }

        private string GetUserLevel(string username) {
            string[] roles = Roles.GetRolesForUser(username);

            if(roles == null || roles.Length < 1) {
                return string.Empty;
            }

            //TODO: Find the highest level
            return roles[0];
        }

        private bool UserHasPermission(string username, string level) {
            switch(level) {
                case "Guru":
                    return (Roles.IsUserInRole(username, "Guru"));

                case "Developer":
                    return (Roles.IsUserInRole(username, "Developer")
                            || Roles.IsUserInRole(username, "Guru"));

                case "Presenter":
                    return (Roles.IsUserInRole(username, "Presenter")
                            || Roles.IsUserInRole(username, "Developer")
                            || Roles.IsUserInRole(username, "Guru"));

                case "Participant":
                    return (Roles.IsUserInRole(username, "Participant")
                            || Roles.IsUserInRole(username, "Presenter")
                            || Roles.IsUserInRole(username, "Developer")
                            || Roles.IsUserInRole(username, "Guru"));
                default:
                    return false;
            }
        }


        //TODO: Need to handle anonymous user case
        //TODO: The presentation identifier is redundant, consider eliminating?
        public XElement Submit(string presentation, string section, XElement userData) {
            log.InfoFormat("Submit User data {0}/{1}", presentation, section);
            try {
                XElement auth = userData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                int sectionInt = int.Parse(section);
                //TODO: Verify that this user can submit data
                DateTime timestamp = DateTime.Now;

                UserResponseData userResponseData = new UserResponseData(presentation, sectionInt);

                foreach(XElement answer in userData.Elements(ns + "Answer")) {
                    int pageInt = Int32.Parse(answer.Attribute("Page").Value);
                    string channel = String.Format("{0}_UD_{1}_{2}", presentation, sectionInt, pageInt);
                    switch(answer.Attribute("Type").Value) {
                        case "MultipleChoice":
                            UserDataUtil.RecordMultipleChoiceAnswer(presentation, sectionInt, user.UserName, answer, timestamp);
                            userResponseData.UserDataElements.Add(UserDataUtil.GetMultipleChoiceUserData(presentation, sectionInt, pageInt));                            
                            break;
                        case "ShortAnswer":
                            UserDataUtil.RecordShortAnswer(presentation, sectionInt, user.UserName, answer, timestamp);
                            userResponseData.UserDataElements.Add(UserDataUtil.GetShortAnswerUserData(presentation, sectionInt, pageInt));
                            break;
                        case "Categories":
                            UserDataUtil.RecordCategoriesAnswer(presentation, sectionInt, user.UserName, answer, timestamp);
                            userResponseData.UserDataElements.Add(UserDataUtil.GetCategoriesUserData(presentation, sectionInt, pageInt));
                            break;
                        default:
                            throw new ArgumentOutOfRangeException("Type", "Type not recognized: " + answer.Attribute("Type").Value);
                    }

                    //publish
                    IAsyncResult result = ActivePublisher.Instance.BeginPublishMessage(channel, userResponseData.ToXml(), null, null);
                    int messageId = ActivePublisher.Instance.EndPublishMessage(result);
                    
                }

                return SuccessMessage();
            } catch(Exception ex) {
                log.Warn("Error submitting user data", ex);
                return ErrorMessage(ex);
            }
        }

        public XElement SubmitErrorReport(XElement reportData) {
            log.Info("Submit Error Report");
            try {
                string source = reportData.Attribute("Source").Value;
                string version = reportData.Attribute("Version").Value;

                string presentationId = null;
                XAttribute presentationNode = reportData.Attribute("Presentation");
                if(presentationNode != null) {
                    presentationId = presentationNode.Value;
                }

                string udid = null;
                XAttribute udidNode = reportData.Attribute("UDID");
                if(udidNode != null) {
                    udid = udidNode.Value;
                }

                //Remove Log Data, if it exists
                XElement logDataNode = reportData.Element(ns + "LogData");
                if(logDataNode != null) {
                    logDataNode.Remove();  //Separate from parent
                }

                //Create a record of this report
                Guid reportID = DataUtil.RecordErrorReport(source, version, presentationId, udid);

                //Store the contents of the document to blob storage
                CloudStorageUtil.StoreErrorReport(reportID.ToString(), source, version, reportData.Value);

                string reportDataTruncated = reportData.Value.Substring(0, Math.Min(reportData.Value.Length, 2000));

                //Send an email indicating the problem
                if(logDataNode != null) {
                    byte[] zippedData = Convert.FromBase64String(logDataNode.Value);

                    using(MemoryStream inStream = new MemoryStream(zippedData)) {
                        using(MemoryStream outStream = new MemoryStream()) {
                            using(GZipStream zipStream = new GZipStream(inStream, CompressionMode.Decompress)) {
                                zipStream.CopyTo(outStream);
                            }

                            //Reset to the beginning of the stream
                            outStream.Position = 0;
                            EmailUtil.SendErrorReportEmail(reportID.ToString(), reportDataTruncated, outStream);
                        }
                    }
                } else {
                    EmailUtil.SendErrorReportEmail(reportID.ToString(), reportDataTruncated);
                }
            
                return SuccessMessage();
            } catch(Exception ex) {
                log.Warn("Cannot submit ErrorReport", ex);
                return ErrorMessage(ex);
            }
        }


        public XElement ResetUserData(string presentation, string section, string page, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                int sectionInt = int.Parse(section);
                int pageInt = int.Parse(page);
                //TODO: Verify that this user can Reset data (presenter)

                UserDataUtil.ResetUserData(presentation, sectionInt, pageInt);

                return SuccessMessage(string.Format("UserData Cleared: {0}/{1}/{2}", presentation, sectionInt, pageInt));
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement ClearAllUserData(string presentation, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                //TODO: Verify that this user can Reset data (presenter)

                UserDataUtil.ResetUserData(presentation);

                return SuccessMessage(string.Format("UserData Cleared: {0}", presentation));
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement GetUserData(string presentation, string section, string page, XElement auth) {

            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to get user data");
                }

                int sectionInt = int.Parse(section);
                int pageInt = 1;  //Default is 1
                if(!(string.IsNullOrEmpty(page))) {
                    pageInt = int.Parse(page);
                }

                UserResponseData userData = new UserResponseData(presentation, sectionInt);

                if(UserDataUtil.HasMultipleChoiceData(presentation, sectionInt, pageInt)) {
                    userData.UserDataElements.Add(UserDataUtil.GetMultipleChoiceUserData(presentation, sectionInt, pageInt));
                } else if(UserDataUtil.HasShortAnswerData(presentation, sectionInt, pageInt)) {
                    userData.UserDataElements.Add(UserDataUtil.GetShortAnswerUserData(presentation, sectionInt, pageInt));
                } else if(UserDataUtil.HasCategoriesData(presentation, sectionInt, pageInt)) {
                    userData.UserDataElements.Add(UserDataUtil.GetCategoriesUserData(presentation, sectionInt, pageInt));
                } else {
                    log.Info("No user data to return");
                    userData.UserDataElements.Add(new EmptyUserData(pageInt));
                }
                return userData.ToXml();
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement GetGroupedUserData(string presentation, string section, string page, XElement auth) {

            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to get grouped user data");
                }

                int sectionInt = int.Parse(section);
                int pageInt = 1;  //Default is 1
                if(!(string.IsNullOrEmpty(page))) {
                    pageInt = int.Parse(page);
                }

                GroupedUserResponseData userData = new GroupedUserResponseData(presentation, sectionInt);

                if(UserDataUtil.HasMultipleChoiceData(presentation, sectionInt, pageInt)) {
                    userData.GroupedUserDataElements = UserDataUtil.GetMultipleChoiceGroupedUserData(presentation, sectionInt, pageInt);
                } else if(UserDataUtil.HasShortAnswerData(presentation, sectionInt, pageInt)) {
                    //TODO: Implement Grouped User Data for short answers
                    //userData.GroupedUserDataElements.Add(UserDataUtil.GetShortAnswerUserData(presentation, sectionInt, pageInt));
                } else if(UserDataUtil.HasCategoriesData(presentation, sectionInt, pageInt)) {
                    //TODO: Implement Grouped User Data for Categories
                    //userData.GroupedUserDataElements.Add(UserDataUtil.GetCategoriesUserData(presentation, sectionInt, pageInt));
                } else {
                    log.Info("No user data to return");
                    userData.GroupedUserDataElements.Add(-1, new EmptyUserData(pageInt));
                }
                return userData.ToXml();
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement GetMultiQuestionGroupedUserData(string presentation, XElement pages) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(pages.Element(ns + "Authentication")));
                if(!AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName)) {
                    return ErrorMessage("User is not authorized to get grouped user data");
                }

                if(pages.IsEmpty){
                    return ErrorMessage("No questions have been requested");                        
                }

                GroupedUserResponseResultsData groupedData = new GroupedUserResponseResultsData(presentation);

                Dictionary<int, int> pagesDict = new Dictionary<int,int>();
                foreach(XElement page in pages.Elements(ns + "Page")) {
                    int pageID = int.Parse(page.Attribute("ID").Value);
                    int correctAnswer = int.Parse(page.Attribute("CorrectAnswer").Value);

                    if(UserDataUtil.HasMultipleChoiceData(presentation, pageID)) {
                        pagesDict.Add(pageID, correctAnswer);
                    }
                }

                groupedData.GroupedResults = UserDataUtil.GetMultipleChoiceGroupedResultsUserData(presentation, pagesDict);

                return groupedData.ToXml();
                
                
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement GetUserDataReport(string presentation, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to get grouped user data");
                }

                UserDataReport userDataReport = new UserDataReport(presentation);

                if(UserDataReportUtil.HasIndividualResponseData(presentation)){
                    List<IndividualResponseData> iud = UserDataReportUtil.GetIndividualResponseData(presentation);

                    foreach(IndividualResponseData individualUser in iud){
                        if(UserDataReportUtil.HasMultipleChoiceData(presentation)){
                            List<MultipleChoiceIndividualData> mcData = UserDataReportUtil.GetMultipleChoiceIndividualData(presentation, individualUser.UserName);
                            foreach(MultipleChoiceIndividualData mc in mcData) {
                                individualUser.UserDataElements.Add(mc);                                
                            }
                        }

                        if(UserDataReportUtil.HasShortAnswerData(presentation)) {
                            List<ShortAnswerIndividualData> saData = UserDataReportUtil.GetShortAnswerIndividualData(presentation, individualUser.UserName);
                            foreach(ShortAnswerIndividualData sa in saData) {
                                individualUser.UserDataElements.Add(sa);
                            }
                        }

                        if(UserDataReportUtil.HasCategoriesAnswerData(presentation)) {
                            List<CategoriesIndividualData> cData = UserDataReportUtil.GetCategoriesIndividualData(presentation, individualUser.UserName);
                            foreach(CategoriesIndividualData c in cData) {
                                individualUser.UserDataElements.Add(c);
                            }
                        }

                        userDataReport.ResponseDataElements.Add(individualUser);
                    }
                }

                return userDataReport.ToXml();
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement SetUserGroup(string presentation, XElement userData) {
            try {
                XElement auth = userData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if (!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to set the presentation state");
                }

                int updatedUsers = 0;
                foreach (XElement userGroup in userData.Elements(ns + "User")) {
                    string username = userGroup.Attribute("ID").Value;
                    int groupID = int.Parse(userGroup.Attribute("Group").Value);

                    DataUtil.SetUserGroup(presentation, username, groupID);
                    updatedUsers++;
                }
               
                return SuccessMessage(string.Format("User group set correctly for {0} users", updatedUsers));
            } catch (Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement RemoveUsersFromGroup(string presentation, XElement userData) {
            try {
                XElement auth = userData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to set the presentation state");
                }

                int updatedUsers = 0;
                foreach(XElement userGroup in userData.Elements(ns + "User")) {
                    string username = userGroup.Attribute("ID").Value;
                    int groupID = int.Parse(userGroup.Attribute("Group").Value);

                    DataUtil.RemoveUserFromGroup(presentation, username, groupID);
                    updatedUsers++;
                }

                return SuccessMessage(string.Format("{0} users removed from group successfully", updatedUsers));
            } catch(Exception ex) {
                return ErrorMessage(ex);                
            }
        }

        public XElement GetGroupUsers(string presentation, XElement userData) {
            try {
                XElement auth = userData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if (!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to set the presentation state");
                }

                XElement userGroup = userData.Element(ns + "Group");
                if (userGroup != null) {
                    int groupID = int.Parse(userGroup.Attribute("ID").Value);

                    return DataUtil.GetGroupUsers(presentation, groupID);
                } else
                    return userGroup;

            } catch (Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement RemovePresentationGroups(string presentation, XElement groupData) {
            try {
                XElement auth = groupData.Element(ns + "Authentication");
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to set the presentation state");
                }

                int updatedGroups = 0;
                foreach(XElement group in groupData.Elements(ns + "Group")) {
                    int groupID = int.Parse(group.Attribute("GroupID").Value);

                    DataUtil.RemovePresentationGroup(presentation, groupID);
                    updatedGroups++;
                }

                return SuccessMessage(string.Format("{0} groups removed successfully", updatedGroups));
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement SetCurrentState(string presentation, string section, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to set the presentation state");
                }

                XElement presentationState = new XElement(ns + "Presentation", new XAttribute("Presentation", presentation), new XAttribute("Section", section));

                string presentationChannel = string.Format("{0}_0", presentation);
                IAsyncResult result = ActivePublisher.Instance.BeginPublishMessage(presentationChannel, presentationState, null, null);
                int messageId = ActivePublisher.Instance.EndPublishMessage(result);

                DataUtil.SetPresentationState(presentation, int.Parse(section), presentationChannel, messageId);

                return SuccessMessage(string.Format("Presentation state updated: {0}", section));
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement GetCurrentState(string presentation, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedParticipant(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to get the presentation state");
                }

               return DataUtil.GetPresentationState(presentation);
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement StartPresentation(string presentation, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to start the presentation");
                }

                return DataUtil.RecordPresentationStart(presentation).ToXml();
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }

        public XElement EndPresentation(string presentation, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));
                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to end the presentation");
                }

                string presentationChannel = string.Format("{0}_0", presentation);
                DataUtil.SetPresentationState(presentation, -1, presentationChannel, 0);

                return SuccessMessage(string.Format("Presentation state updated: {0}", -1));
            } catch(Exception ex) {
                return ErrorMessage(ex);
            }
        }
        
        public XElement RenamePresentation(string presentation, string name, XElement auth) {
            try {
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                if(!(AuthorizationUtil.IsAuthorizedPresenter(presentation, user.UserName))) {
                    return ErrorMessage("User is not authorized to rename the presentation");
                }

                //Retrieve the presentation info
                Presentation pres = DataUtil.RenamePresentation(presentation, name);
                PresentationHeader presHeader = new PresentationHeader(pres);

                //return it.
                return presHeader.ToXml();

            } catch (Exception ex) {
                throw new Exception("Cannot create new presentation", ex);
            }
        }

        public XElement NewPresentation(string name, XElement auth) {

            try{
                MembershipUser user = Authenticate(AuthorizationUtil.FromAuthenticationXML(auth));

                if(!(UserHasPermission(user.UserName, "Presenter"))) {
                    return ErrorMessage("User does not have permission to create a presentation", "Authorization");
                }

                //Store and retrieve the presentation info
                Presentation pres = DataUtil.CreateNewPresentation(name);
                DataUtil.AddPresenter(pres, user.UserName);
                PresentationHeader presHeader = new PresentationHeader(pres);
                //return it.
                return presHeader.ToXml();

            } catch (Exception ex) {
                return ErrorMessage(ex);
                //throw new Exception("Cannot create new presentation", ex);
                //throw new UnauthorizedAccessException();
            }
        }

        public static XElement SuccessMessage(string message = null) {
            XElement mNode = new XElement("Success");
            if(!(string.IsNullOrWhiteSpace(message))) {
                mNode.Add(new XAttribute("Message", message));
            }
            return mNode;
        }

        public static XElement ErrorMessage(string message, string domain = @"Default") {
            XElement err = new XElement("Error", new XAttribute("Message", message), new XAttribute("Domain", domain));
            return err;
        }

        public static XElement ErrorMessage(Exception ex) {
            string domain = "Default";
            if(ex is UnauthorizedAccessException) {
                domain = "Authorization";
            }
            XElement err = new XElement("Error", new XAttribute("Message", ex.Message), new XAttribute("Domain", domain));
            return err;
        }


        private static string ErrorPage(string message, Uri redirect = null) {
            StringBuilder sb = new StringBuilder();

            sb.AppendFormat("<html><head><title>Download Error</title></head>\n<body>{0}", message);

            if(redirect != null) {
                sb.AppendFormat("You can try your request again here:<br />\n");
                sb.AppendFormat("<a href={0}>{0}</a>\n", redirect.AbsoluteUri);
            }
            sb.AppendFormat("</body></html>");
            return sb.ToString();
        }
    }
}
